๐Ÿ“ ๋น„๊ด€์  ๋ฝ vs ๋‚™๊ด€์  ๋ฝ + MyBatis ์ ์šฉ ๊ฐ€์ด๋“œ

NOTE

๋ฉ€ํ‹ฐ ์„œ๋ฒ„/ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ์˜ ๋™์‹œ์„ฑ ์ œ์–ด โ€” ๋น„๊ด€์  ๋ฝ๊ณผ ๋‚™๊ด€์  ๋ฝ์˜ ๊ฐœ๋…ยทMyBatis ๊ตฌํ˜„ยท์žฌ์‹œ๋„ ๋กœ์งยท์„ ํƒ ๊ธฐ์ค€.

๐Ÿ“Œ ๊ฐœ๋…

1. ์™œ DB ๋ฝ์ด ํ•„์š”ํ•œ๊ฐ€?

๋ฉ€ํ‹ฐ ์„œ๋ฒ„ / ๋ฉ€ํ‹ฐ ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹ค์Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์žฌ๊ณ  ์ค‘๋ณต ์ฐจ๊ฐ
  • ํฌ์ธํŠธ ์ด์ค‘ ์‚ฌ์šฉ
  • ๊ณ„์ขŒ ์ž”์•ก ์˜ค์—ผ
  • ๋งˆ์ง€๋ง‰ ์ˆ˜์ • ๋ฎ์–ด์“ฐ๊ธฐ (Lost Update)

Java Lock์€ ๋‹จ์ผ JVM์—์„œ๋งŒ ์œ ํšจ

๐Ÿ‘‰ ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” DB ๋ ˆ๋ฒจ ์ œ์–ด ํ•„์š”

2. ๋น„๊ด€์  ๋ฝ (Pessimistic Lock)

๐Ÿ”’ ๊ฐœ๋…

โ€œ์ถฉ๋Œ์€ ๋ฐ˜๋“œ์‹œ ๋ฐœ์ƒํ•œ๋‹คโ€๋ผ๊ณ  ๊ฐ€์ •. ์ฝ๋Š” ์ˆœ๊ฐ„๋ถ€ํ„ฐ ๋ฝ์„ ๊ฑด๋‹ค.

SQL

SELECT * FROM product
WHERE id = 1
FOR UPDATE;
  • ํ•ด๋‹น row์— Exclusive Lock
  • ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ๊นŒ์ง€ ์œ ์ง€

MyBatis ์ ์šฉ

<select id="selectForUpdate" resultType="Product">
    SELECT *
    FROM product
    WHERE id = #{id}
    FOR UPDATE
</select>

Service ๋ ˆ๋ฒจ (์ค‘์š”)

@Transactional
public void decreaseStock(Long productId, int quantity) {
 
    Product product = productMapper.selectForUpdate(productId);
 
    if (product.getStock() < quantity) {
        throw new IllegalStateException("์žฌ๊ณ  ๋ถ€์กฑ");
    }
 
    productMapper.updateStock(productId, quantity);
}

โš ๏ธ ๋ฐ˜๋“œ์‹œ ๊ธฐ์–ตํ•  ๊ฒƒ

  • @Transactional ํ•„์ˆ˜
  • ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๊ฐ€ ๊ธธ๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜
  • ๋ฐ๋“œ๋ฝ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ

3. ๋‚™๊ด€์  ๋ฝ (Optimistic Lock)

๐ŸŸข ๊ฐœ๋…

โ€œ์ถฉ๋Œ์€ ๊ฑฐ์˜ ์—†๋‹คโ€๋ผ๊ณ  ๊ฐ€์ •. DB์— ๋ฌผ๋ฆฌ์  ๋ฝ์„ ๊ฑธ์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  version ๊ฐ’์„ ๋น„๊ตํ•œ๋‹ค.

ํ…Œ์ด๋ธ” ๊ตฌ์กฐ

id | stock | version

4. MyBatis์—์„œ ๋‚™๊ด€์  ๋ฝ ๊ตฌํ˜„ ๋ฐฉ๋ฒ• โญ (ํ•ต์‹ฌ)

MyBatis๋Š” JPA์ฒ˜๋Ÿผ @Version ์ž๋™ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์ด ์—†๋‹ค.

๐Ÿ‘‰ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

4.1 SELECT ์‹œ version ์กฐํšŒ

<select id="selectById" resultType="Product">
    SELECT id, stock, version
    FROM product
    WHERE id = #{id}
</select>

4.2 UPDATE ์‹œ version ๋น„๊ต

<update id="updateWithVersion">
    UPDATE product
    SET stock = #{stock},
        version = version + 1
    WHERE id = #{id}
      AND version = #{version}
</update>

๐Ÿ‘‰ ํ•ต์‹ฌ: WHERE ์ ˆ์— version ํฌํ•จ

4.3 Service ๋ ˆ๋ฒจ ๊ตฌํ˜„ (์‹ค๋ฌด ํŒจํ„ด)

@Transactional
public void decreaseStock(Long productId, int quantity) {
 
    Product product = productMapper.selectById(productId);
 
    if (product.getStock() < quantity) {
        throw new IllegalStateException("์žฌ๊ณ  ๋ถ€์กฑ");
    }
 
    int updatedRows = productMapper.updateWithVersion(
            productId,
            product.getStock() - quantity,
            product.getVersion()
    );
 
    if (updatedRows == 0) {
        throw new IllegalStateException("๋™์‹œ์„ฑ ์ถฉ๋Œ ๋ฐœ์ƒ");
    }
}

4.4 ์™œ updatedRows๋ฅผ ํ™•์ธํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

์ถฉ๋Œ ๋ฐœ์ƒ โ†’ version ๊ฐ’์ด ๋‹ฌ๋ผ์ง
โ†’ UPDATE ์˜ํ–ฅ row = 0
โ†’ ์‹คํŒจ ๊ฐ์ง€ ๊ฐ€๋Šฅ

์ด ์ฒดํฌ๋ฅผ ์•ˆ ํ•˜๋ฉด ๐Ÿ‘‰ ๋‚™๊ด€์  ๋ฝ์ด ๋ฌด์˜๋ฏธํ•ด์ง„๋‹ค.

5. ์žฌ์‹œ๋„ ๋กœ์ง (์‹ค๋ฌด์—์„œ ๊ฑฐ์˜ ํ•„์ˆ˜)

public void decreaseWithRetry(Long productId, int quantity) {
 
    int retry = 3;
 
    while (retry-- > 0) {
        try {
            decreaseStock(productId, quantity);
            return;
        } catch (IllegalStateException e) {
            if (!e.getMessage().contains("๋™์‹œ์„ฑ")) {
                throw e;
            }
        }
    }
 
    throw new IllegalStateException("์žฌ์‹œ๋„ ์‹คํŒจ");
}

6. ๋น„๊ด€์  vs ๋‚™๊ด€์  ๋น„๊ต

๊ตฌ๋ถ„๋น„๊ด€์  ๋ฝ๋‚™๊ด€์  ๋ฝ
๋ฝ ์‹œ์ SELECT ์‹œUPDATE ์‹œ
DB ๋ฌผ๋ฆฌ์  ๋ฝ์žˆ์Œ์—†์Œ
์„ฑ๋Šฅ๋‚ฎ์Œ๋†’์Œ
๋ฐ๋“œ๋ฝ๊ฐ€๋Šฅ์—†์Œ
์žฌ์‹œ๋„ํ•„์š” ์—†์Œํ•„์š”

7. ์‹ค๋ฌด ์„ ํƒ ๊ธฐ์ค€

๐Ÿ”ฅ ์žฌ๊ณ  ์ฐจ๊ฐ / ๊ธˆ์œต ๐Ÿ‘‰ ๋น„๊ด€์  ๋ฝ

  • ์ ˆ๋Œ€ ํ‹€๋ฆฌ๋ฉด ์•ˆ ๋จ
  • ๊ฐ•ํ•œ ์ •ํ•ฉ์„ฑ ํ•„์š”

๐Ÿ“ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • / ์ผ๋ฐ˜ CRUD ๐Ÿ‘‰ ๋‚™๊ด€์  ๋ฝ

  • ์ถฉ๋Œ ํ™•๋ฅ  ๋‚ฎ์Œ
  • ์„ฑ๋Šฅ ์ค‘์š”

8. MyBatis ํ™˜๊ฒฝ์—์„œ ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜

  • โŒ version ์ปฌ๋Ÿผ ์•ˆ ๋งŒ๋“ฆ
  • โŒ WHERE ์ ˆ์— version ์•ˆ ๋„ฃ์Œ
  • โŒ update row count ์ฒดํฌ ์•ˆ ํ•จ
  • โŒ ์žฌ์‹œ๋„ ๋กœ์ง ์—†์Œ
  • โŒ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ๊ณผ๋„

9. ๊ตฌ์กฐ์ ์œผ๋กœ ์ดํ•ดํ•˜๊ธฐ

๋น„๊ด€์  ๋ฝ = ๋จผ์ € ์ž ๊ทธ๊ณ  ์‹œ์ž‘
๋‚™๊ด€์  ๋ฝ = ๋‚˜์ค‘์— ๋น„๊ตํ•˜๊ณ  ํŒ๋‹จ

๐Ÿ”Ÿ ๋ฉด์ ‘์šฉ ์ •๋ฆฌ ๋ฉ˜ํŠธ

MyBatis์—์„œ๋Š” ๋‚™๊ด€์  ๋ฝ์„ ์ž๋™ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— version ์ปฌ๋Ÿผ์„ ๋‘๊ณ  UPDATE ์‹œ version์„ ๋น„๊ตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. UPDATE ๊ฒฐ๊ณผ row count๊ฐ€ 0์ด๋ฉด ์ถฉ๋Œ๋กœ ํŒ๋‹จํ•˜๊ณ  ์žฌ์‹œ๋„ ๋กœ์ง์„ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽฏ ์ตœ์ข… ํ•ต์‹ฌ ์š”์•ฝ

  • MyBatis๋Š” ๋‚™๊ด€์  ๋ฝ ์ˆ˜๋™ ๊ตฌํ˜„
  • UPDATE ์‹œ version ๋น„๊ต ํ•„์ˆ˜
  • ์˜ํ–ฅ row ์ˆ˜ ํ™•์ธ ํ•„์ˆ˜
  • ์žฌ์‹œ๋„ ๋กœ์ง ์„ค๊ณ„ ์ค‘์š”
  • ๊ฐ•ํ•œ ์ •ํ•ฉ์„ฑ = ๋น„๊ด€์ 
  • ๊ณ ์„ฑ๋Šฅ = ๋‚™๊ด€์ 